package com.yy.router.compile;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import com.yy.annotation.Route;
import com.yy.annotation.model.RouteMeta;
import com.yy.router.compile.utils.Consts;
import com.yy.router.compile.utils.Log;
import com.yy.router.compile.utils.Utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import static javax.lang.model.element.Modifier.PUBLIC;

@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
    private Filer mFiler;
    private Log mLog;
    private Types mTypesUtils;
    private Elements mElementsUtils;
    private Map<String, List<RouteMeta>> mRouteGroup = new HashMap<>();

    private Map<String, String> mRootMap = new TreeMap<>();

    private String moduleName;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mLog = Log.newLog(processingEnvironment.getMessager());
        mFiler = processingEnvironment.getFiler();
        mTypesUtils = processingEnvironment.getTypeUtils();
        mElementsUtils = processingEnvironment.getElementUtils();
        //参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
        Map<String, String> options = processingEnv.getOptions();

        if (!Utils.isEmpty(options)) {
            moduleName = options.get(Consts.ARGUMENTS_NAME);
        }
        mLog.i("RouteProcessor Parmaters:" + moduleName);
        if (Utils.isEmpty(moduleName)) {
            throw new RuntimeException("Not set Processor Parmaters.");
        }
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotationSets = new HashSet<>();
        annotationSets.add(Consts.ANN_TYPE_ROUTE);
        return annotationSets;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!Utils.isEmpty(set)) {
            Set<? extends Element> routeSets =
                    roundEnvironment.getElementsAnnotatedWith(Route.class);
            //处理路由
            if (!Utils.isEmpty(routeSets)) {
                try {
                    parseRoute(routeSets);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
            return true;
        }
        return false;
    }

    private void parseRoute(Set<? extends Element> set) throws IOException {
        //获取全类信息
        TypeElement typeElement = mElementsUtils.getTypeElement(Consts.ACTIVITY);
        TypeMirror mirror = typeElement.asType();
        for (Element element : set) {
            RouteMeta routeMeta = null;
            //获取到类信息
            TypeMirror typeMirror = element.asType();
            if (mTypesUtils.isSubtype(typeMirror, mirror)) {
                Route routeAnnotation = element.getAnnotation(Route.class);
                routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, routeAnnotation, element);
            } else {
                throw new RuntimeException("[Just support Activity/Service]");
            }
            //分组
            categories(routeMeta);
            TypeElement routeGroup = mElementsUtils.getTypeElement(Consts.IROUTE_GROUP);

            TypeElement iRouteRoot = mElementsUtils.getTypeElement(Consts.IROUTE_ROOT);
            generateGroup(routeGroup);
            generatedRoot(iRouteRoot, routeGroup);
        }
    }

    private void categories(RouteMeta routeMeta) {
        if (routeVerity(routeMeta)) {
            List<RouteMeta> routeMetas = mRouteGroup.get(routeMeta.getGroup());
            if (Utils.isEmpty(routeMetas)) {
                List<RouteMeta> routeMetaList = new ArrayList<>();
                routeMetaList.add(routeMeta);
                mRouteGroup.put(routeMeta.getGroup(), routeMetaList);
            } else {
                routeMetas.add(routeMeta);
            }
        } else {
            mLog.i("Group Info error:" + routeMeta.getPath());
        }
    }

    private boolean routeVerity(RouteMeta routeMeta) {
        String path = routeMeta.getPath();
        String group = routeMeta.getGroup();
        if (Utils.isEmpty(path) || !path.startsWith("/")) {
            return false;
        }

        if (Utils.isEmpty(group)) {
            String substring = path.substring(1, path.indexOf("/", 1));
            if (Utils.isEmpty(substring)) {
                return false;
            }
            routeMeta.setGroup(substring);
        }
        return true;
    }

    private void generateGroup(TypeElement routeGroup) throws IOException {
        //参数类型
        ParameterizedTypeName atlas =
                ParameterizedTypeName.get(
                        ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ClassName.get(RouteMeta.class)
                );
        //参数名字
        ParameterSpec spec = ParameterSpec.builder(atlas, "atlas").build();
        //创建每一个分组
        for (Map.Entry<String, List<RouteMeta>> entry : mRouteGroup.entrySet()) {
            MethodSpec.Builder methodBuild =
                    MethodSpec.methodBuilder(Consts.METHOD_LOAD_INTO)
                            .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(TypeName.VOID)
                            .addParameter(spec);

            String groupName = entry.getKey();
            List<RouteMeta> routeMetas = entry.getValue();
            for (RouteMeta routeMeta : routeMetas) {
                mLog.i("group: " + groupName + "   routeMeta: " + routeMeta.toString());
                methodBuild.addStatement(
                        "atlas.put($S,$T.build($T.$L,$T.class,$S,$S))",
                        routeMeta.getPath(),
                        ClassName.get(RouteMeta.class),
                        ClassName.get(RouteMeta.Type.class),
                        routeMeta.getType(),
                        ClassName.get((TypeElement) routeMeta.getElement()),
                        routeMeta.getPath().toLowerCase(),
                        routeMeta.getGroup().toLowerCase()
                );
            }
            //类名
            String groupClassName = Consts.NAME_OF_GROUP + groupName;
            TypeSpec typeBuild = TypeSpec.classBuilder(groupClassName)
                    .addSuperinterface(ClassName.get(routeGroup))
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(methodBuild.build())
                    .build();

            JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE, typeBuild).build()
                    .writeTo(mFiler);
            mRootMap.put(groupName, groupClassName);

        }
    }

    private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) throws IOException {
        //类型 Map<String,Class<? extends IRouteGroup>> routes>
        //Wildcard 通配符
        ParameterizedTypeName routes = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
                )
        );

        //参数 Map<String,Class<? extends IRouteGroup>> routes> routes
        ParameterSpec rootParamSpec = ParameterSpec.builder(routes, "routes")
                .build();
        //函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
                (Consts.METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);

        //函数体
        for (Map.Entry<String, String> entry : mRootMap.entrySet()) {
            loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry
                    .getKey(), ClassName.get(Consts.PACKAGE_OF_GENERATE_FILE, entry.getValue
                    ()));
        }
        //生成 $Root$类
        String rootClassName = Consts.NAME_OF_ROOT + moduleName;
        JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(rootClassName)
                        .addSuperinterface(ClassName.get(iRouteRoot))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfRootBuilder.build())
                        .build()
        ).build().writeTo(mFiler);

        mLog.i("Generated RouteRoot: " + Consts.PACKAGE_OF_GENERATE_FILE + "." + rootClassName);
    }

}
